/*
 * Copyright (C) 2020  GreenWaves Technologies, SAS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

/* 
 * Authors: Germain Haugou, GreenWaves Technologies (germain.haugou@greenwaves-technologies.com)
 */

#include <vp/vp.hpp>
#include <vp/itf/io.hpp>
#include <vp/itf/wire.hpp>
#include <stdio.h>
#include <string.h>

#include "vp/itf/clock.hpp"

#include "archi/chips/gap9_v2/apb_soc_ctrl/apb_soc_ctrl_regs.h"
#include "archi/chips/gap9_v2/apb_soc_ctrl/apb_soc_ctrl_regfields.h"
#include "archi/chips/gap9_v2/apb_soc_ctrl/apb_soc_ctrl_gvsoc.h"

using namespace std::placeholders;

#define L2_NB_BANKS 16
#define L1_NB_BANKS 2

class apb_soc_ctrl : public vp::component
{

public:
    apb_soc_ctrl(js::config *config);

    int build();
    void start();
    void reset(bool active);

    static vp::io_req_status_e req(void *__this, vp::io_req *req);

private:
    //vp::io_req_status_e sleep_ctrl_req(int reg_offset, int size, bool is_write, uint8_t *data);
    //vp::io_req_status_e l2_btrim_stdby_req(int reg_offset, int size, bool is_write, uint8_t *data);
    vp::io_req_status_e fll_ctrl_req(int reg_offset, int size, bool is_write, uint8_t *data);
    vp::io_req_status_e l1_pwr_ctrl_req(int reg_offset, int size, bool is_write, uint8_t *data);
    vp::io_req_status_e l2_pwr_ctrl_req(int reg_offset, int size, bool is_write, uint8_t *data);
    void reg_osc_ctrl_req(uint64_t reg_offset, int size, uint8_t *value, bool is_write);
    void dbg_ctrl_req(uint64_t reg_offset, int size, uint8_t *value, bool is_write);
    void sleep_ctrl_info_req(uint64_t reg_offset, int size, uint8_t *value, bool is_write);
    void sleep_ctrl_req(uint64_t reg_offset, int size, uint8_t *value, bool is_write);
    void clk_div_ref_fast_pow2_req(uint64_t reg_offset, int size, uint8_t *value, bool is_write);
    void l2_pwr_active_req(uint64_t reg_offset, int size, uint8_t *value, bool is_write);

    static void ref_clock_sync(void *__this, bool value);
    static void fast_clock_sync(void *__this, bool value);

    static void bootsel_sync(void *__this, int value);
    static void confreg_ext_sync(void *__this, uint32_t value);
    static void wakeup_rtc_sync(void *__this, bool wakeup);
    static void wakeup_gpio_sync(void *__this, int value, int gpio);
    void set_wakeup(int value);
    void sync_dm_available(uint32_t mask);

    vp::trace trace;
    vp::trace info;
    vp::io_slave in;

    vp::wire_slave<int> bootsel_itf;
    vp::wire_master<uint32_t> bootaddr_itf;
    vp::wire_master<bool> cluster_reset_itf;
    vp::wire_master<bool> cluster_power_itf;
    vp::wire_master<bool> cluster_power_irq_itf;
    vp::wire_master<bool> cluster_clock_gate_irq_itf;
    vp::wire_master<int> event_itf;
    vp::wire_master<bool> ref_clk_ctrl_itf;
    vp::wire_master<bool> fast_clk_ctrl_itf;
    vp::wire_slave<bool> wakeup_rtc_itf;
    vp::wire_master<bool> wakeup_out_itf;
    vp::wire_master<unsigned int> wakeup_seq_itf;
    std::vector<vp::wire_master<bool>> dm_hart_available_itf;
    std::vector<vp::wire_master<bool> *> l2_power_ctrl_itfs;

    std::vector<vp::wire_slave<int>> wakeup_gpio_itf;

    vp::wire_master<uint32_t> confreg_soc_itf;
    vp::wire_slave<uint32_t> confreg_ext_itf;

    int cluster_power_event;
    int cluster_clock_gate_event;

    uint32_t core_status;
    uint32_t bootaddr;
    uint32_t pmu_bypass;
    bool cluster_reset;
    bool cluster_power;
    bool cluster_clock_gate;
    uint32_t l2_vddme;
    uint32_t l2_vddde;
    uint32_t l2_standby;
    uint32_t l1_power;
    uint32_t l1_standby;
    int nb_harts;
    int fast_clock_div_counter;
    int fast_clock_divided_value;
    int nb_l2_shared_banks;

    unsigned int extwake_sync;

    //vp_apb_soc_ctrl_sleep_ctrl r_sleep_ctrl;
    //vp_apb_soc_ctrl_safe_l2_btrim_stdby  r_l2_btrim_stdby;
    //vp_apb_soc_ctrl_safe_fll_ctrl        r_fll_ctrl;
    //vp_apb_soc_ctrl_safe_l1_pwr_ctrl     r_l1_pwr_ctrl;
    //vp_apb_soc_ctrl_safe_l2_pwr_ctrl     r_l2_pwr_ctrl;

    vp::reg_32 jtag_reg_ext;

    vp_regmap_apb_soc_ctrl regmap;

    int wakeup;

    vp::clock_slave ref_clock_itf;
    vp::clock_slave fast_clock_itf;
    vp::clock_master ref_clock_muxed_itf;
};

apb_soc_ctrl::apb_soc_ctrl(js::config *config)
    : vp::component(config)
{
}

void apb_soc_ctrl::set_wakeup(int value)
{
    if (this->wakeup == 0)
    {
        this->wakeup = value;
        this->wakeup_out_itf.sync(value);
    }
}

void apb_soc_ctrl::ref_clock_sync(void *__this, bool value)
{
    apb_soc_ctrl *_this = (apb_soc_ctrl *)__this;

    if (_this->regmap.ref_clk_mux.get() == 0)
    {
        _this->ref_clock_muxed_itf.sync(value);
    }
}

void apb_soc_ctrl::fast_clock_sync(void *__this, bool value)
{
    apb_soc_ctrl *_this = (apb_soc_ctrl *)__this;
    int limit = 1 << _this->regmap.clk_div_ref_fast_pow2.divider_get();
    bool reached = true;

    _this->trace.msg(vp::trace::LEVEL_TRACE, "Updating counter due to fast clock raising edge (counter: %d, limit: %d)\n", _this->fast_clock_div_counter, limit);

    if (_this->regmap.clk_div_ref_fast_pow2.en_get())
    {
        _this->fast_clock_div_counter++;
        if (_this->fast_clock_div_counter == limit)
        {
            _this->fast_clock_div_counter = 0;
        }
        else
        {
            reached = false;
        }
    }

    if (reached)
    {
        _this->trace.msg(vp::trace::LEVEL_TRACE, "Reached limit, raising fast divided edge (value: %d)\n", _this->fast_clock_divided_value);

        if (_this->regmap.ref_clk_mux.get())
        {
            _this->trace.msg(vp::trace::LEVEL_TRACE, "Propagating to muxed ref clock\n");

            _this->ref_clock_muxed_itf.sync(_this->fast_clock_divided_value);
            _this->fast_clock_divided_value ^= 1;
        }
    }
}

#if 0
vp::io_req_status_e apb_soc_ctrl::l2_btrim_stdby_req(int reg_offset, int size, bool is_write, uint8_t *data)
{
  this->r_l2_btrim_stdby.access(reg_offset, size, data, is_write);

  if (is_write)
  {
    this->trace.msg("Modified SAFE_L2_BTRIM_STDBY (btrim: 0x%x, stdby_n: 0x%x)\n",
      this->r_l2_btrim_stdby.btrim_get(),
      this->r_l2_btrim_stdby.stdby_n_get()
    );

    uint32_t standby = ~this->r_l2_btrim_stdby.stdby_n_get();

    for (int i=0; i<L2_NB_BANKS; i++)
    {
      int new_power = (standby >> i) & 1;
      int current_power = (this->l2_standby >> i) & 1;

      if (new_power != current_power)
      {
        if (new_power)
        {
          this->info.msg("Activating cut standby (cut: %d)\n", i);
        }
        else
        {
          this->info.msg("Deactivating cut standby (cut: %d)\n", i);
        }
      }
    }

    this->l2_standby = standby;
  }

  return vp::IO_REQ_OK;
}



vp::io_req_status_e apb_soc_ctrl::fll_ctrl_req(int reg_offset, int size, bool is_write, uint8_t *data)
{
  this->r_fll_ctrl.access(reg_offset, size, data, is_write);

  if (is_write)
  {
    this->trace.msg("Modified SAFE_FLL_CTRL (fll2_pwd: %d, fll3_pwd: %d, fll2_rstb: %d, fll3_rstb: %d)\n",
      this->r_fll_ctrl.fll2_pwd_get(),
      this->r_fll_ctrl.fll3_pwd_get(),
      this->r_fll_ctrl.fll2_rstb_get(),
      this->r_fll_ctrl.fll3_rstb_get()
    );
  }

  return vp::IO_REQ_OK;
}



vp::io_req_status_e apb_soc_ctrl::l1_pwr_ctrl_req(int reg_offset, int size, bool is_write, uint8_t *data)
{
  this->r_l1_pwr_ctrl.access(reg_offset, size, data, is_write);

  if (is_write)
  {
    this->trace.msg("Modified SAFE_L1_PWR_CTRL (btrim: 0x%x, stdby_n: 0x%x, pwd: 0x%x)\n",
      this->r_l1_pwr_ctrl.btrim_get(),
      this->r_l1_pwr_ctrl.stdby_n_get(),
      this->r_l1_pwr_ctrl.pwd_get()
    );

    uint32_t l1_power = ~this->r_l1_pwr_ctrl.pwd_get();

    for (int i=0; i<L1_NB_BANKS; i++)
    {
      int new_power = (l1_power >> i) & 1;
      int current_power = (this->l1_power >> i) & 1;

      if (new_power != current_power)
      {
        if (new_power)
        {
          this->info.msg("Powering-up L1 cut memory array (cut: %d)\n", i);
        }
        else
        {
          this->info.msg("Powering-down L1 cut memory array (cut: %d)\n", i);
        }
      }
    }
    this->l1_power = l1_power;


    uint32_t standby = ~this->r_l1_pwr_ctrl.stdby_n_get();

    for (int i=0; i<L1_NB_BANKS; i++)
    {
      int new_power = (standby >> i) & 1;
      int current_power = (this->l1_standby >> i) & 1;

      if (new_power != current_power)
      {
        if (new_power)
        {
          this->info.msg("Activating L1 cut standby (cut: %d)\n", i);
        }
        else
        {
          this->info.msg("Deactivating L1 cut standby (cut: %d)\n", i);
        }
      }
    }

    this->l1_standby = standby;

  }

  return vp::IO_REQ_OK;
}



vp::io_req_status_e apb_soc_ctrl::l2_pwr_ctrl_req(int reg_offset, int size, bool is_write, uint8_t *data)
{
  this->r_l2_pwr_ctrl.access(reg_offset, size, data, is_write);

  if (is_write)
  {
    this->trace.msg("Modified SAFE_L2_PWR_CTRL (vddde_n: 0x%x, vddme_n: 0x%x)\n",
      this->r_l2_pwr_ctrl.vddde_n_get(),
      this->r_l2_pwr_ctrl.vddme_n_get()
    );

    uint32_t vddme = ~this->r_l2_pwr_ctrl.vddme_n_get();

    for (int i=0; i<L2_NB_BANKS; i++)
    {
      int new_power = (vddme >> i) & 1;
      int current_power = (this->l2_vddme >> i) & 1;

      if (new_power != current_power)
      {
        if (new_power)
        {
          this->info.msg("Powering-up cut memory array (cut: %d)\n", i);
        }
        else
        {
          this->info.msg("Powering-down cut memory array (cut: %d)\n", i);
        }
      }
    }

    this->l2_vddme = vddme;

    uint32_t vddde = ~this->r_l2_pwr_ctrl.vddde_n_get();

    for (int i=0; i<L2_NB_BANKS; i++)
    {
      int new_power = (vddde >> i) & 1;
      int current_power = (this->l2_vddde >> i) & 1;

      if (new_power != current_power)
      {
        if (new_power)
        {
          this->info.msg("Powering-up cut periphery (cut: %d)\n", i);
        }
        else
        {
          this->info.msg("Powering-down cut periphery (cut: %d)\n", i);
        }
      }
    }

    this->l2_vddde = vddde;
  }

  return vp::IO_REQ_OK;
}
#endif


void apb_soc_ctrl::sleep_ctrl_info_req(uint64_t reg_offset, int size, uint8_t *value, bool is_write)
{
    if (!is_write)
    {
        this->regmap.sleep_ctrl_info.set_32(this->regmap.sleep_ctrl.get_32());
        this->regmap.sleep_ctrl_info.update(reg_offset, size, value, is_write);
    }
}


void apb_soc_ctrl::sleep_ctrl_req(uint64_t reg_offset, int size, uint8_t *value, bool is_write)
{
    this->regmap.sleep_ctrl.update(reg_offset, size, value, is_write);

#if 0
    //this->r_sleep_ctrl.access(reg_offset, size, data, is_write);

    if (is_write)
    {
        // this->trace.msg("Modified SLEEP_CTRL (reboot: %d, smartwake_en: %d, rtcwake_en: %d, extwake_type: %d, extwake_en: %d, mram_wakestate: %d, cluster_wakestate: %d, ret_mem: 0x%4.4x)\n",
        //   this->r_sleep_ctrl.reboot_get(),
        //   this->r_sleep_ctrl.smartwake_en_get(),
        //   this->r_sleep_ctrl.rtcwake_en_get(),
        //   this->r_sleep_ctrl.extwake_type_get(),
        //   this->r_sleep_ctrl.extwake_en_get(),
        //   this->r_sleep_ctrl.mram_wakestate_get(),
        //   this->r_sleep_ctrl.cluster_wakestate_get(),
        //   this->r_sleep_ctrl.ret_mem_get()
        // );
    }
    else
    {
        this->trace.msg("Clearing wakeup signal\n");

        this->wakeup = 0;
    }

    return vp::IO_REQ_OK;

#endif

#if 0
    if (is_write)
    {
      uint32_t value = *(uint32_t *)data;
      _this->extwake_sel = (value >> 6) & 0x1f;   // GPIO selection for wakeup
      _this->extwake_type = (value >> 11) & 0x3;  // GPIO wakeup type (raising edge, etc)
      _this->extwake_en = (value >> 13) & 0x1;    // GPIO wakeup enabled
      _this->cfg_wakeup = (value >> 14) & 0x3;    // PMU sequence used to wakeup
      _this->boot_type = (value >> 18) & 0x3;
      _this->wakeup_seq_itf.sync(_this->cfg_wakeup);
    }
    else
    {
      _this->set_wakeup(0);
      *(uint32_t *)data = (_this->extwake_sel << 6) | (_this->extwake_type << 11) | (_this->extwake_en << 13) | (_this->cfg_wakeup << 14) | (_this->boot_type << 18);
    }
#endif
}

vp::io_req_status_e apb_soc_ctrl::req(void *__this, vp::io_req *req)
{
    apb_soc_ctrl *_this = (apb_soc_ctrl *)__this;

    uint64_t offset = req->get_addr();
    uint8_t *data = req->get_data();
    uint64_t size = req->get_size();
    bool is_write = req->get_is_write();

    vp::io_req_status_e err = vp::IO_REQ_OK;

    _this->trace.msg("Apb_soc_ctrl access (offset: 0x%x, size: 0x%x, is_write: %d)\n", offset, size, is_write);

    int reg_id = offset / 4;
    int reg_offset = offset % 4;

    _this->regmap.access(offset, size, data, is_write);

    if (offset == APB_SOC_CTRL_CORESTATUS_OFFSET)
    {
        if (!is_write)
        {
            *(uint32_t *)data = _this->core_status;
        }
        else
        {
            // We are writing to the status register, the 31 LSBs are the return value of the platform and the last bit
            // makes the platform exit when set to 1
            _this->core_status = *(uint32_t *)data;

            if ((_this->core_status >> APB_SOC_CTRL_CORESTATUS_EOC_BIT) & 1)
            {
                _this->clock->stop_retain(-1);
                _this->clock->stop_engine(_this->core_status & 0x7fffffff);
            }
            else
            {
                uint32_t value = *(uint32_t *)data;
                if (value == 0x0BBAABBA)
                {
                    _this->power.get_engine()->start_capture();
                }
                else if (value == 0x0BBADEAD)
                {
                    _this->power.get_engine()->stop_capture();
                }
            }
        }
    }
    else if (offset == APB_SOC_CTRL_JTAGREG_OFFSET)
    {
        if (is_write)
        {
            _this->confreg_soc_itf.sync(*(uint32_t *)data);
        }
        else
        {
            *(uint32_t *)data = _this->jtag_reg_ext.get() << APB_SOC_CTRL_JTAGREG_EXTERNAL_BIT;
        }
    }
#if 0
  else if (offset == APB_SOC_SAFE_L2_BTRIM_STDBY_OFFSET)
  {
    err = _this->l2_btrim_stdby_req(reg_offset, size, is_write, data);
  }
  else if (offset == APB_SOC_SAFE_FLL_CTRL_OFFSET)
  {
    err = _this->fll_ctrl_req(reg_offset, size, is_write, data);
  }
  else if (offset == APB_SOC_SAFE_L1_PWR_CTRL_OFFSET)
  {
    err = _this->l1_pwr_ctrl_req(reg_offset, size, is_write, data);
  }
  else if (offset == APB_SOC_SAFE_L2_PWR_CTRL_OFFSET)
  {
    err = _this->l2_pwr_ctrl_req(reg_offset, size, is_write, data);
  }
#endif
    else if (offset == APB_SOC_CTRL_FC_BOOT_OFFSET)
    {
        if (is_write)
        {
            _this->trace.msg("Setting boot address (addr: 0x%x)\n", *(uint32_t *)data);
            if (_this->bootaddr_itf.is_bound())
                _this->bootaddr_itf.sync(*(uint32_t *)data);

            _this->bootaddr = *(uint32_t *)data;
        }
        else
            *(uint32_t *)data = _this->bootaddr;
    }
#if 0
  else if (offset == APB_SOC_BYPASS_OFFSET)
  {
    if (is_write)
    {
      _this->trace.msg("Setting PMU bypass (addr: 0x%x)\n", *(uint32_t *)data);

      _this->pmu_bypass = *(uint32_t *)data;

      bool new_cluster_power = (_this->pmu_bypass >> 3) & 1;
      bool new_cluster_reset = (_this->pmu_bypass >> 13) & 1;
      bool new_cluster_clock_gate = (_this->pmu_bypass >> 10) & 1;

      if (_this->cluster_reset != new_cluster_reset)
      {
        if (_this->cluster_reset_itf.is_bound())
        {
          _this->cluster_reset_itf.sync(~new_cluster_reset);
        }

        _this->cluster_reset = new_cluster_reset;
      }

      if (_this->cluster_power != new_cluster_power)
      {
        _this->trace.msg("Setting cluster power (power: 0x%d)\n", new_cluster_power);

        if (_this->cluster_power_itf.is_bound())
        {
          _this->cluster_power_itf.sync(new_cluster_power);
        }

        _this->trace.msg("Triggering soc event (event: 0x%d)\n", _this->cluster_power_event);
        _this->event_itf.sync(_this->cluster_power_event);

        if (_this->cluster_power_irq_itf.is_bound())
        {
          _this->cluster_power_irq_itf.sync(true);
        }
      }

      if (_this->cluster_clock_gate != new_cluster_clock_gate)
      {
        _this->trace.msg("Triggering soc event (event: 0x%d)\n", _this->cluster_clock_gate_event);
        _this->event_itf.sync(_this->cluster_clock_gate_event);

        if (_this->cluster_clock_gate_irq_itf.is_bound())
        {
          _this->cluster_clock_gate_irq_itf.sync(true);
        }
      }

      _this->cluster_power = new_cluster_power;
      _this->cluster_clock_gate = new_cluster_clock_gate;

    }
    else
    {
      *(uint32_t *)data = _this->pmu_bypass;
    }
  }
  else if (offset == APB_SOC_BYPASS_OFFSET)
  {
  }
#endif
    else
    {
    }

    return err;
}

void apb_soc_ctrl::bootsel_sync(void *__this, int value)
{
    apb_soc_ctrl *_this = (apb_soc_ctrl *)__this;
    _this->regmap.bootsel.bootsel_set(value);
}

void apb_soc_ctrl::wakeup_gpio_sync(void *__this, int value, int gpio)
{
#if 0
  apb_soc_ctrl *_this = (apb_soc_ctrl *)__this;
  if (_this->r_sleep_ctrl.extwake_en_get())
  {
    int old_value = _this->extwake_sync;

    _this->extwake_sync = value;

    switch (_this->r_sleep_ctrl.extwake_type_get())
    {
      case 0: {
        if (old_value == 0 && _this->extwake_sync == 1)
          _this->set_wakeup(1);
        break;
      }
      case 1: {
        if (old_value == 1 && _this->extwake_sync == 0)
          _this->set_wakeup(1);
        break;
      }
      case 2: {
        if (_this->extwake_sync == 1)
          _this->set_wakeup(1);
        break;
      }
      case 3: {
        if (_this->extwake_sync == 0)
          _this->set_wakeup(1);
        break;
      }
    }
  }
#endif
}

void apb_soc_ctrl::wakeup_rtc_sync(void *__this, bool wakeup)
{
    apb_soc_ctrl *_this = (apb_soc_ctrl *)__this;
    _this->trace.msg(vp::trace::LEVEL_DEBUG, "Received RTC wakeup\n");
    _this->wakeup_out_itf.sync(1);
}

void apb_soc_ctrl::confreg_ext_sync(void *__this, uint32_t value)
{
    apb_soc_ctrl *_this = (apb_soc_ctrl *)__this;
    _this->jtag_reg_ext.set(value);
}

int apb_soc_ctrl::build()
{
    traces.new_trace("trace", &trace, vp::DEBUG);
    traces.new_trace("info", &info, vp::INFO);
    in.set_req_meth(&apb_soc_ctrl::req);
    new_slave_port("input", &in);

    new_master_port("bootaddr", &this->bootaddr_itf);

    new_master_port("event", &event_itf);

    new_master_port("ref_clk_ctrl", &ref_clk_ctrl_itf);
    new_master_port("fast_clk_ctrl", &fast_clk_ctrl_itf);

    new_master_port("cluster_power", &cluster_power_itf);
    new_master_port("cluster_reset", &cluster_reset_itf);
    new_master_port("cluster_power_irq", &cluster_power_irq_itf);

    new_master_port("cluster_clock_gate_irq", &cluster_clock_gate_irq_itf);

    this->wakeup_rtc_itf.set_sync_meth(&apb_soc_ctrl::wakeup_rtc_sync);
    new_slave_port("wakeup_rtc", &this->wakeup_rtc_itf);

    this->wakeup_gpio_itf.resize(1);
    for (int i = 0; i < 1; i++)
    {
        this->wakeup_gpio_itf[i].set_sync_meth_muxed(&apb_soc_ctrl::wakeup_gpio_sync, i);
        new_slave_port("wakeup_gpio" + std::to_string(i), &this->wakeup_gpio_itf[i]);
    }

    new_master_port("wakeup_out", &this->wakeup_out_itf);

    new_master_port("wakeup_seq", &this->wakeup_seq_itf);

    confreg_ext_itf.set_sync_meth(&apb_soc_ctrl::confreg_ext_sync);
    this->new_slave_port("confreg_ext", &this->confreg_ext_itf);

    this->new_master_port("confreg_soc", &this->confreg_soc_itf);

    this->new_reg("jtag_reg_ext", &this->jtag_reg_ext, 0, false);

    bootsel_itf.set_sync_meth(&apb_soc_ctrl::bootsel_sync);
    new_slave_port("bootsel", &bootsel_itf);

    cluster_power_event = this->get_js_config()->get("cluster_power_event")->get_int();
    cluster_clock_gate_event = this->get_js_config()->get("cluster_clock_gate_event")->get_int();

    core_status = 0;
    this->jtag_reg_ext.set(0);

    // This one is in the always-on domain and so it is reset only when the
    // component is powered-up
    this->wakeup = 0;

    this->extwake_sync = 0;

    this->nb_harts = this->get_js_config()->get_int("nb_harts");
    this->dm_hart_available_itf.resize(this->nb_harts);
    for (int i = 0; i < this->nb_harts; i++)
    {
        this->new_master_port("dm_hart_available_" + std::to_string(i), &this->dm_hart_available_itf[i]);
    }

    //this->new_reg("sleep_ctrl", &this->r_sleep_ctrl, 0, false);
    //this->new_reg("l2_btrim_stdby", &this->r_l2_btrim_stdby, 0, false);
    //this->new_reg("fll_ctrl", &this->r_fll_ctrl, 0, false);
    //this->new_reg("l1_pwr_ctr", &this->r_l1_pwr_ctrl, 0, false);
    //this->new_reg("l2_pwr_ctrl", &this->r_l2_pwr_ctrl, 0, false);

    //this->r_sleep_ctrl.set(0);
    //this->r_l1_pwr_ctrl.set(0xFFFF0);
    //this->r_l2_btrim_stdby.set(0x30);

    this->l2_vddme = (1 << L2_NB_BANKS) - 1;
    this->l2_vddde = (1 << L2_NB_BANKS) - 1;
    this->l2_standby = 0;

    this->l1_power = (1 << L1_NB_BANKS) - 1;
    this->l1_standby = 0;

    this->regmap.build(this, &this->trace);
    this->regmap.sleep_ctrl_info.register_callback(std::bind(&apb_soc_ctrl::sleep_ctrl_info_req, this, _1, _2, _3, _4));
    this->regmap.sleep_ctrl.register_callback(std::bind(&apb_soc_ctrl::sleep_ctrl_req, this, _1, _2, _3, _4));
    this->regmap.dbg_ctrl.register_callback(std::bind(&apb_soc_ctrl::dbg_ctrl_req, this, _1, _2, _3, _4));
    this->regmap.reg_osc_ctrl.register_callback(std::bind(&apb_soc_ctrl::reg_osc_ctrl_req, this, _1, _2, _3, _4));
    this->regmap.clk_div_ref_fast_pow2.register_callback(std::bind(&apb_soc_ctrl::clk_div_ref_fast_pow2_req, this, _1, _2, _3, _4));
    this->regmap.l2_pwr_active.register_callback(std::bind(&apb_soc_ctrl::l2_pwr_active_req, this, _1, _2, _3, _4));

    this->fast_clock_itf.set_sync_meth(&apb_soc_ctrl::fast_clock_sync);
    this->new_slave_port("fast_clock", &fast_clock_itf);

    this->ref_clock_itf.set_sync_meth(&apb_soc_ctrl::ref_clock_sync);
    this->new_slave_port("ref_clock", &ref_clock_itf);

    this->new_master_port("ref_clock_muxed", &this->ref_clock_muxed_itf);

    this->nb_l2_shared_banks = this->get_js_config()->get_child_int("nb_l2_shared_banks");
    for (int i=0; i<this->get_js_config()->get_child_int("nb_l2_shared_banks"); i++)
    {
        vp::wire_master<bool> *itf = new vp::wire_master<bool>();
        this->l2_power_ctrl_itfs.push_back(itf);
        this->new_master_port("l2_power_ctrl_" + std::to_string(i), itf);
    }

    return 0;
}

void apb_soc_ctrl::sync_dm_available(uint32_t mask)
{
    for (int i = 0; i < this->nb_harts; i++)
    {
        if (this->dm_hart_available_itf[i].is_bound())
        {
            this->dm_hart_available_itf[i].sync((mask >> i) & 1);
        }
    }
}

void apb_soc_ctrl::reg_osc_ctrl_req(uint64_t reg_offset, int size, uint8_t *value, bool is_write)
{
    this->regmap.reg_osc_ctrl.update(reg_offset, size, value, is_write);

    if (is_write)
    {
        this->fast_clk_ctrl_itf.sync(this->regmap.reg_osc_ctrl.fast_osc_en_get());
        this->ref_clk_ctrl_itf.sync(this->regmap.reg_osc_ctrl.slow_osc_en_get());
    }
}

void apb_soc_ctrl::dbg_ctrl_req(uint64_t reg_offset, int size, uint8_t *value, bool is_write)
{
    this->regmap.dbg_ctrl.update(reg_offset, size, value, is_write);

    if (is_write)
    {
        this->sync_dm_available(this->regmap.dbg_ctrl.get_32());
    }
}

void apb_soc_ctrl::clk_div_ref_fast_pow2_req(uint64_t reg_offset, int size, uint8_t *value, bool is_write)
{
    this->regmap.clk_div_ref_fast_pow2.update(reg_offset, size, value, is_write);

    if (is_write)
    {
        this->fast_clock_div_counter = 0;
    }
}


void apb_soc_ctrl::l2_pwr_active_req(uint64_t reg_offset, int size, uint8_t *value, bool is_write)
{
    this->regmap.l2_pwr_active.update(reg_offset, size, value, is_write);

    if (is_write)
    {
        uint32_t value = this->regmap.l2_pwr_active.get();
        for (int i=0; i<this->nb_l2_shared_banks; i++)
        {
            this->l2_power_ctrl_itfs[i]->sync(!((value >> i) & 1));
        }
    }
}


void apb_soc_ctrl::reset(bool active)
{
    this->regmap.reset(active);

    if (active)
    {
        this->cluster_power = false;
        this->cluster_clock_gate = false;
        this->fast_clock_div_counter = 0;
        this->fast_clock_divided_value = 0;

        this->sync_dm_available(this->regmap.dbg_ctrl.get_32());

        this->fast_clk_ctrl_itf.sync(this->regmap.reg_osc_ctrl.fast_osc_en_get());
        this->ref_clk_ctrl_itf.sync(this->regmap.reg_osc_ctrl.slow_osc_en_get());
    }
}

void apb_soc_ctrl::start()
{
    this->clock->stop_retain(1);
}

extern "C" vp::component *vp_constructor(js::config *config)
{
    return new apb_soc_ctrl(config);
}
